# -*-coding:utf-8-*-
import re
import model
import os
'''
debug option
'''
module_analyzer_debug = True
level_1_debug_print = False
disable_all_print = False
wanna_print_to_do = False


def print_comment(the_str):
    if disable_all_print is True:
        return
    if the_str[0:6] == "to do:":
        if wanna_print_to_do is True:
            print(the_str)
    elif the_str[0:4] == "(L1)":
        if level_1_debug_print is True:
            print(the_str)
    elif module_analyzer_debug is True:
        print(the_str)


def name_filter(word):
    if word != "":
        if word.find('->') > 0:
            word = word[:word.find('->')].strip()
        if word.find('.') > 0:
            word = word[:word.find('.')].strip()
        if word.find('[') > 0:
            word = word[:word.find('[')].strip()
        word = word.replace('*', '').strip()
        word = word.replace('+', '').strip()
        word = word.replace('-', '').strip()
        word = word.replace('&', '').strip()
        word = word.replace('|', '').strip()
    return word


def is_a_name(the_str):
    is_name = False
    if the_str != "":
        if the_str[0] == '*':
            the_str = the_str[1:].strip()
            if the_str == "":
                return False
        if (the_str[0] <= 'Z'
                and the_str[0] >= 'A') or (the_str[0] <= 'z'
                                           and the_str[0] >= 'a'):
            pattern = re.compile(r'[a-zA-Z_0-9]+')
            ret = pattern.match(the_str)
            if ret is not None:
                if ret.group(0) == the_str:
                    is_name = True
    return is_name

def eat_function_ptr_unit(line_left, unit):
    line = line_left
    unit_left = unit
    found_type = ""
    unit_left ,result = eat_units(unit_left,'(')
    if result is True:
        unit_left ,result = eat_units(unit_left,'*')
        if result is True and unit.find(')') > 0:
            line_left, result = eat_units(line_left, unit[2:unit.find(')')]) 
            if result is True:
                found_type = unit[2:unit.find(')')]
                return line_left, True, found_type
    return line.strip(), False , found_type


def eat_units(line_left, unit):
    if line_left[0:len(unit)] == unit:
        line_left = line_left[len(unit):]
        return line_left.strip(), True
    else:
        return line_left.strip(), False


def kick_units(line_left, unit):
    if line_left[-len(unit):] == unit:
        line_left = line_left[:-len(unit)]
        return line_left.strip(), True
    else:
        return line_left.strip(), False


def get_called_parameters(line_left):
    func_name = ""
    input_args = []

    if line_left.find("(") >= 0:
        func_name = line_left[:line_left.find("(")]
        input_args = line_left[line_left.find("(") +
                               1:line_left.find(")")].split(',')
        line_left = line_left[line_left.find(")") + 1:].strip()
    return func_name, input_args, line_left


def is_complete_word_in_line(word, line):
    pattern = re.compile(r'\d|\w|\'|\"')
    ret = False
    if line.find(word) == 0:
        if word == line:
            ret = True
        elif len(line) > len(word):
            if len(pattern.findall(line[len(word)])) == 0:
                ret = True
    elif line.find(word) > 0:
        byte_tmp = ""
        if line.find(word) + len(word) < len(line):
            byte_tmp = line[line.find(word) + len(word)]

        if len(pattern.findall(byte_tmp)) == 0 and len(
                pattern.findall(line[line.find(word) - 1])) == 0:
            ret = True
        else:
            print_comment("incomplete word found!")
    return ret


def macro_analyze(line_left):
    key = ""
    macro_info = {}

    if line_left != "":
        key, macro_info["input"], macro_info[
            "content"] = get_called_parameters(line_left)

    return key, macro_info


class c_code_reader:

    module_info = model.module_info()
    module_dir = ""
    analyzing_dir = ""
    prev_dirs = []
    current_state = ""
    current_handler = None
    current_path = ""
    dic_temp = {}
    content = []
    is_in_function = False
    is_in_comment = False
    function_tmp = None
    function_line = ""
    e_data_is_found = False
    e_data_type_tmp = ""
    crashed = False
    macro_level = 0
    macro_call_incomplete = False
    compile_flag = True
    compile_level_list= []
    unit_handled = False
    handlers = []
    expand_include_path = []
    eof_mark = ""
    no_use_init_flag = False

    def key_word_filter(self, line_left):

        key_words = [
            "const", "volatile", "auto", "return", "register", "static"
        ]
        for key_word in key_words:
            if is_complete_word_in_line(key_word, line_left):
                line_left = line_left.replace(key_word, "")
        return line_left.strip()

    def comment_jumpper(self, line_left):
        if self.is_in_comment:
            if line_left.find("*/") >= 0:
                self.is_in_comment = False
                print_comment("comment out")
                line_left = line_left[line_left.find("*/") + 2:]
            else:
                line_left = ""
        else:
            if line_left.find("//") >= 0:
                line_left = line_left[:line_left.find("//")]
            if line_left.find("/*") >= 0:
                if line_left.find("*/") < 0:
                    self.is_in_comment = True
                    print_comment("comment in")
                    line_left = line_left[:line_left.find("/*")]
                else:
                    line_left = line_left[:line_left.find(
                        "/*")] + line_left[line_left.find("*/") + 2:]
        if self.is_in_function is False and self.current_state == "":
            line_left = line_left.strip()
        else:
            line_left = line_left.replace('\n', '')
        return line_left

    def macro_switch(self, line):
        if line.strip() != "" and line.strip()[0] == "#":
            line_left, result = eat_units(line.strip(), "#")
            if self.compile_flag is True:
                line_left, result = eat_units(line_left, "ifndef")
                if result is True:
                    print_comment("#ifndef case")
                    self.macro_level += 1
                    if line_left in self.module_info.defined_dic.keys():
                        self.compile_flag = False
                    self.compile_level_list.append([self.macro_level, self.compile_flag, line])
                    return ""

                line_left, result = eat_units(line_left, "ifdef")
                if result is True:
                    print_comment("#ifdef case")
                    self.macro_level += 1
                    self.compile_flag = line_left in self.module_info.defined_dic.keys()
                    self.compile_level_list.append([self.macro_level, self.compile_flag, line])
                    return ""
                
                line_left, result = eat_units(line_left, "if")
                if result is True:
                    try:
                        self.macro_level += 1
                        self.compile_level_list.append([self.macro_level, self.compile_flag, line])
                        self.compile_flag = eval('(((' + line_left + ') > 0)is not False)')
                    except:
                        print_comment("macro error")
                        self.compile_flag = False
                    return ""
                line_left, result = eat_units(line_left, "elif")
                if result is True and self.compile_level_list[-1][1] != -1:
                    try:
                        self.compile_flag = eval('(((' + line_left + ') > 0)is not False)')
                    except:
                        print_comment("macro error")
                        self.compile_flag = False
                    return ""

            else:
                line_left, result = eat_units(line_left, "if")
                if result is True:
                    self.macro_level += 1
                    self.compile_level_list.append([self.macro_level, -1, line])
                else:
                    line_left, result = eat_units(line_left, "elif")
                    if result is True and self.compile_level_list[-1][1] != -1 and self.compile_flag is False:
                        try:
                            self.compile_flag = eval('(((' + line_left + ') > 0)is not False)')
                        except:
                            print_comment("macro error")
                            self.compile_flag = False
                        return ""

            line_left, result = eat_units(line_left, "else")
            if result is True:
                if len(self.compile_level_list)>0 and self.compile_level_list[-1][1] != -1:
                    print_comment("#else")
                    self.compile_flag = not self.compile_flag
                    self.compile_level_list[-1][1] = self.compile_flag
                    return ""

            line_left, result = eat_units(line_left, "endif")
            if result is True:
                if self.macro_level > 0:
                    self.macro_level -= 1
                if self.macro_level == 0:
                    self.compile_flag = True
                    self.unit_handled = True
                if len(self.compile_level_list) > 0:
                    self.compile_level_list.pop()
                    if len(self.compile_level_list) > 0 and self.compile_level_list[-1][1] != -1:
                        self.compile_flag =  self.compile_level_list[-1][1]
                print_comment("#endif")
                return line_left.strip()

            line_left, result = eat_units(line_left, "error")
            if result is True:
                return ""

        if self.compile_flag is False:
            return ""
        else:
            return line

    def define_analyze(self, line):

        if self.compile_flag is True and (
            (line != "" and line[0] == "#")
                or self.current_state == "multi line define"):
            if line != "" and line[-1] == '\\':
                line = line[:-1]
                self.current_state = "multi line define"
                self.line_tmp = self.line_tmp + " " + line.strip()
                self.current_handler = self.define_analyze
                return ""
            elif self.current_state == "multi line define":
                self.current_state = ""
                line = self.line_tmp.strip() + " " + line
                self.line_tmp = ""
            line_left, result = eat_units(line, "#")
            line_left, result = eat_units(line_left, "define")
            if result is True:
                key = ""
                value = ""
                ret = re.match(r'(.*?)\s(.*)', line_left)
                if ret is not None:
                    key = ret.group(1).strip()
                    value = ret.group(2).strip()
                    if key.find("(") > 0:
                        '''
                        forget key and value, and anlyze the macro again
                        '''
                        key, value = macro_analyze(line_left)

                    line_left = ""
                    print_comment("to do: add define with mult lines")
                else:
                    key = line_left
                    print_comment("to do: define with (x)")

                if key not in self.module_info.defined_dic.keys():

                    # if value == "":
                    #     value = key
                    self.module_info.defined_dic[key] = value
                else:
                    # self.crashed = True
                    print_comment("redefined")
                    self.module_info.defined_dic.update({key: value})
                self.unit_handled = True
                print_comment("defined_dic append " + key + ":" + str(value))
                return ""

        return line

    def get_macro_input_arg(self, values_code):
        arg_cnt = 0
        input_args = values_code.split(',')
        while arg_cnt < len(input_args) and len(input_args) > 1:
            while input_args[arg_cnt].count('(') != input_args[arg_cnt].count(
                    ')'):
                input_args[arg_cnt] += ',' + input_args[arg_cnt + 1]
                input_args.remove(input_args[arg_cnt + 1])
            arg_cnt += 1
        return input_args

    def is_define_code(self, line):
        line, result = eat_units(line, "#")
        if result is True:
            line, result = eat_units(line, "define")
            if result is True:
                return True
            line, result = eat_units(line, "ifdef")
            if result is True:
                return True
            line, result = eat_units(line, "endif")
            if result is True:
                return True
        return False

    def defined_symbol_expand(self, insert_index):
        if insert_index == 1761 or self.content[insert_index].find('enum kwp_response_code ')>0:
            print_comment("hit break point")
        if self.content[insert_index].find('#') >= 0 and self.content[insert_index].find('include') > 0:
            return
        if self.compile_flag is True and self.content[insert_index].strip(
        ) != "" and self.is_in_comment is False :
            while True and not self.is_define_code(self.content[insert_index].strip()):
                scan_cnt = len(self.module_info.defined_dic.keys())
                for defined_key in self.module_info.defined_dic.keys():
                    if is_complete_word_in_line(defined_key,
                                                self.content[insert_index]):
                        '''
                        judge if it is a dictionary
                        '''
                        if not isinstance(
                                self.module_info.defined_dic[defined_key],
                                dict):

                            self.content[insert_index] = self.content[
                                insert_index].replace(
                                    defined_key,
                                    self.module_info.defined_dic[defined_key])
                            '''
                            loop until we can't replace any more
                            for the nested macro.
                            '''
                            if defined_key != self.module_info.defined_dic[
                                    defined_key]:
                                scan_cnt -= 1
                        else:
                            '''
                            to assemble a completly macro call line.
                            '''
                            if self.content[insert_index].count(
                                    '(') != self.content[insert_index].count(
                                        ')'):
                                self.macro_call_incomplete = True
                                return
                            '''
                            expand the content in macro
                            '''
                            left_brace_index = self.content[insert_index].find(
                                defined_key) + len(defined_key)
                            '''
                            find right brace index   
                            eg:  func( ABC(xx(bb),(cc)) )
                                        ^             ^  
                                        macro         right brace index
                            '''
                            right_brace_index = left_brace_index + 1
                            brace_level = 1
                            while right_brace_index < len(
                                    self.content[insert_index]):
                                if self.content[insert_index][
                                        right_brace_index] == '(':
                                    brace_level += 1
                                if self.content[insert_index][
                                        right_brace_index] == ')':
                                    brace_level -= 1
                                if brace_level == 0:
                                    break
                                right_brace_index += 1

                            if self.content[insert_index][
                                    left_brace_index] == '(':
                                expand_line = self.module_info.defined_dic[
                                    defined_key]['content']
                                input_args = self.get_macro_input_arg(
                                    self.content[insert_index]
                                    [left_brace_index + 1:right_brace_index])
                                replace_args = self.module_info.defined_dic[
                                    defined_key]['input']
                                if len(input_args) == len(replace_args):
                                    i = 0
                                    for i in range(len(replace_args)):
                                        expand_line = expand_line.replace(
                                            replace_args[i].strip(),
                                            input_args[i].strip())

                                self.content[insert_index] = self.content[
                                    insert_index].replace(
                                        self.content[insert_index]
                                        [self.content[insert_index].
                                         find(defined_key):right_brace_index +
                                         1], expand_line)
                                '''
                                if is assembled, should separate lines by ;
                                '''
                                if self.macro_call_incomplete is True:
                                    self.macro_call_incomplete = False
                                    if self.content[insert_index].find(
                                            ';'
                                    ) < len(self.content[insert_index]) - 1:
                                        expand_lines = self.content[
                                            insert_index].split(';')
                                        self.content[
                                            insert_index] = expand_lines[
                                                0] + ';'
                                        expand_lines.remove(expand_lines[0])
                                        while (len(expand_lines)):
                                            insert_line = expand_lines.pop(
                                            ).strip()
                                            if insert_line != "":
                                                self.content.insert(
                                                    insert_index + 1,
                                                    insert_line + ';')

                                scan_cnt -= 1
                if scan_cnt == len(self.module_info.defined_dic.keys()):
                    break

    def include_path_analyze(self, line):
        if self.compile_flag is True and line != "" and line[0] == "#":
            line_left, result = eat_units(line, "#")
            line_left, result = eat_units(line_left, "include")
            if result is True:
                result = re.findall(r'\"(.*)\"', line_left)
                if result is None or line_left[0] != '"':
                    result = re.findall(r'<(.*)>', line_left)
                    if result is None or line_left[0] != '<':
                        self.crashed = True
                    return ""

                elif result[0] not in self.expand_include_path:
                    self.expand_include_path.append(result[0])
                    self.unit_handled = True
                    print_comment("included path: " + result[0])
                return line_left[(len(result[0]) + 2):]
        return line

    def path_anlyze(self, path):
        path = path.replace('\\', '/')
        path = path.replace('//', '/')
        while path.find('/../') > 0:
            left_side_path = path[:path.find('/../')]
            right_side_path = path[path.find('/../') + 4:]
            if left_side_path.rfind('/') > 0:
                left_side_path = left_side_path[:left_side_path.rfind('/') + 1]
            path = left_side_path + right_side_path
        return path

    def include_file_expand(self, insert_line, is_end_in_expand_file):

        if is_end_in_expand_file and len(self.prev_dirs) > 0:
            self.analyzing_dir = self.prev_dirs.pop()

        for include_path in self.expand_include_path:
            self.prev_dirs.append(self.analyzing_dir)
            self.analyzing_dir = self.path_anlyze(
                self.analyzing_dir + '/' + os.path.dirname(include_path))
            c_file = self.path_anlyze(self.analyzing_dir + "/" +
                                      os.path.basename(include_path))

            if c_file in self.module_info.include_paths:
                self.expand_include_path.remove(include_path)
                self.analyzing_dir = self.prev_dirs.pop()
                continue
            if os.path.exists(c_file):
                fp = open(c_file)
            else:
                print_comment(c_file + 'not exist!')
                return
            lines = []
            try:
                for line in fp:
                    lines.append(line)
            except Exception:
                fp = open(c_file, encoding="windows-1252")
                for line in fp:
                    lines.append(line)
            lines.insert(0, "//include file expand start:" + c_file)
            lines.insert(0, "//not use")
            lines.append(self.eof_mark)
            lines.append("//include file expand end:" + c_file)
            print_comment("expand path:" + include_path)
            while (len(lines)):
                self.content.insert(insert_line, lines.pop())
            fp.close()
            if (c_file not in self.module_info.include_paths):
                self.module_info.include_paths.append(c_file)
            self.expand_include_path.pop()

    def find_defined_type(self, line):
        line_left = line 
        result = False
        found_type = ""
        signed_flag = ""
        for flag_name in model.sign_flags:
            line_left, result = eat_units(line, flag_name)
            if result is True and is_complete_word_in_line(flag_name, line):
                signed_flag = flag_name + " "
                for type_name in model.base_types:
                    line_left, result = eat_units(line_left, type_name)
                    if result is True and is_complete_word_in_line(
                            type_name, line):
                        found_type = type_name
                        return line_left, result, signed_flag + found_type
        for type_name in self.module_info.type_info.type_def_list.keys():
            line_left, result = eat_units(line, type_name)
            if result is True and is_complete_word_in_line(type_name, line):
                if self.module_info.type_info.type_def_list[type_name] == "":
                    for struct_obj in self.module_info.type_info.struct_list:
                        line_left, result = eat_units(line, struct_obj.name)
                        if result is True and is_complete_word_in_line(
                                struct_obj.name, line):
                            found_type = struct_obj.name
                            return line_left, result, found_type
                else:
                    found_type = type_name
                return line_left, result, found_type
            '''
            function pointer solution
            '''
            line_left, result, found_type = eat_function_ptr_unit(line, type_name)
            if result is True:
                return line_left, result, found_type
        for type_name in model.base_types:
            line_left, result = eat_units(line, type_name)
            if result is True and is_complete_word_in_line(type_name, line):
                found_type = type_name
                return line_left, result, signed_flag + found_type
        for enum_obj in self.module_info.type_info.enum_list:
            line_left, result = eat_units(line, enum_obj.name)
            if result is True and is_complete_word_in_line(
                    enum_obj.name, line):
                found_type = enum_obj.name
                return line_left, result, found_type
        for struct_obj in self.module_info.type_info.struct_list:
            line_left, result = eat_units(line, struct_obj.name)
            if result is True and is_complete_word_in_line(
                    struct_obj.name, line):
                found_type = struct_obj.name
                return line_left, result, found_type
        return line, result, found_type

    def find_type(self, line):
        line_left = line
        result = False
        found_type = ""
        line_left, result, found_type = self.find_defined_type(line_left)
        if result is False:
            '''
            currently undefine struct,just add
            '''
            tmp, result = eat_units(line_left, "struct")
            if result is True and tmp != "":
                line_left = tmp
                # line_left = line_left[len(line_left[:line_left.find(' ')]
                #                           ):].strip()
                found_type = "struct " + line_left[:line_left.find(' ')]
                self.module_info.type_info.type_def_list.update(
                    {found_type: ''})
            '''
            we dont care the return code, currently
            '''
            line_left, dont_care_result = eat_units(line_left, "return")

        return line_left, result, found_type

    def is_base_type_or_enum(self, type_str):
        for type_name in model.base_types:
            if type_str == type_name:
                return True
        for enum_obj in self.module_info.type_info.enum_list:
            if type_str == enum_obj.name:
                return True
        return False

    line_tmp = ""

    def assemble_line(self, line_left):
        if line_left.replace(' ', '').replace('\t', '') == "":
            return ""
        if line_left.strip(
        )[-1] == ";" or line_left[-1] == "{" or line_left[-1] == "}":
            if self.line_tmp != "":
                line_left = self.line_tmp + " " + line_left
                self.line_tmp = ""
            if self.no_use_init_flag is True:
                self.no_use_init_flag = False
                line_left = ""
            if line_left.find("}") >= 0:
                return line_left
            return line_left.strip()

        self.line_tmp = self.line_tmp + " " + line_left.strip()
        self.current_handler = self.assemble_line
        return ""
